package com.asgard.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;

/**
 * 该类定义了若干加密算法函数。
 */
public class CryptoUtils {
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 获取私钥 PKCS8 格式字节数组
     *
     * @param privateKey 私钥
     * @return 私钥字节数组
     */
    public static byte[] getECPrivateKeyPKCS8Bytes(PrivateKey privateKey) {
        BCECPrivateKey priKey = (BCECPrivateKey) privateKey;
        return priKey.getEncoded();
    }

    /**
     * 获取私钥普通编码格式字节数组
     *
     * @param privateKey 私钥
     * @return 私钥字节数组
     */
    public static byte[] getECPrivateKeyBytes(PrivateKey privateKey) {
        BCECPrivateKey priKey = (BCECPrivateKey) privateKey;
        BigInteger s = priKey.getS();
        byte[] b = s.toByteArray();
        return toByte32(b);
    }

    /**
     * 获取私钥普通编码格式的16进制字符串
     *
     * @param privateKey 私钥
     * @return 16进制字符串
     */
    public static String getECPrivateKeyHexString(PrivateKey privateKey) {
        return Hex.toHexString(getECPrivateKeyBytes(privateKey));
    }

    /**
     * 按照X509格式将公钥转换为字节数组
     *
     * @param publicKey 公钥
     * @return 字节数组
     */
    public static byte[] getECPublicKeyX509Bytes(PublicKey publicKey) {
        BCECPublicKey pubKey = (BCECPublicKey) publicKey;
        return pubKey.getEncoded();
    }

    /**
     * 按照一般格式将公钥转换成为字节数组
     *
     * @param pubKey 公钥
     * @return 字节数组
     */
    public static byte[] getECPublicKeyBytes(PublicKey pubKey) {
        BCECPublicKey pubKey2 = (BCECPublicKey) pubKey;
        ECPoint p = EC5Util.convertPoint(pubKey2.getParams(), pubKey2.getW(), false);
        return p.getEncoded(false);
    }

    /**
     * 按照压缩格式将公钥转换为字节数组
     *
     * @param publicKey 公钥
     * @return 字节数组
     */
    public static byte[] getECPublicKeyCompressedBytes(PublicKey publicKey) {
        BCECPublicKey pubKey = (BCECPublicKey) publicKey;
        ECPoint p = EC5Util.convertPoint(pubKey.getParams(), pubKey.getW(), false);
        return p.getEncoded(true);
    }

    /**
     * 获取公钥普通编码格式的16进制字符串
     *
     * @param publicKey 公钥
     * @return 16进制字符串
     */
    public static String getECPublicKeyHexString(PublicKey publicKey) {
        return Hex.toHexString(getECPublicKeyBytes(publicKey));
    }

    /**
     * 采用RipeMD160算法计算数据的哈希值。
     *
     * @param data 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeRipeMD160(byte[] data) {
        try {
            MessageDigest md = MessageDigest.getInstance("RipeMD160");
            return md.digest(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("缺少RipeMD160支持");
        }
    }

    /**
     * 采用SHA-1算法计算数据的哈希值。
     *
     * @param data 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha1(byte[] data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            return md.digest(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("缺少SHA-1支持");
        }
    }

    /**
     * 采用SHA-256算法计算数据的哈希值。
     *
     * @param data 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha256(byte[] data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            return md.digest(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("缺少SHA-256支持");
        }
    }

    /**
     * 采用两次SHA-256算法计算数据的哈希值。
     *
     * @param data 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha256Twice(byte[] data) {
        return encodeSha256(encodeSha256(data));
    }

    /**
     * 采用SHA-256算法计算数据的哈希值。
     *
     * @param data1 数据
     * @param data2 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha256(byte[] data1, byte[] data2) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(data1);
            md.update(data2);
            return md.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("缺少SHA-256支持");
        }
    }

    /**
     * 采用SHA-256算法计算数据的哈希值。
     *
     * @param data1 数据
     * @param data2 数据
     * @param data3 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha256(byte[] data1, byte[] data2, byte[] data3) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(data1);
            md.update(data2);
            md.update(data3);
            return md.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("缺少SHA-256支持");
        }
    }

    /**
     * 采用两次SHA-256算法计算数据的哈希值。
     *
     * @param data1 数据
     * @param data2 数据
     * @return 数据的哈希值
     */
    public static byte[] encodeSha256Twice(byte[] data1, byte[] data2) {
        return encodeSha256(encodeSha256(data1, data2));
    }

    private static char[] base58Alphabet;
    private static Map<Character, Integer> base58Value;

    static {
        String alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
        base58Alphabet = alphabet.toCharArray();
        base58Value = new HashMap<>();
        for (int i = 0; i < base58Alphabet.length; i++)
            base58Value.put(base58Alphabet[i], i);
    }

    /**
     * 将字节数组表示的数据转换为Base58格式。
     *
     * @param data 字节数组表示的数据
     * @return Base58格式数据
     */
    public static String encodeBase58(byte[] data) {
        // 处理串首0
        int numLeadZero = 0;
        int len = data.length;
        while (numLeadZero < len && data[numLeadZero] == 0)
            numLeadZero++;
        // 将字节数组转换为int数组，即Base256数组
        int len2 = len - numLeadZero;
        int[] v = new int[len2];
        ByteArrayInputStream bin = new ByteArrayInputStream(data, numLeadZero, len2);
        for (int i = 0; i < len2; i++)
            v[i] = bin.read();
        // 转换求余
        StringBuilder sb = new StringBuilder();
        int baseNum = base58Alphabet.length;
        while (true) {
            // 判断是否继续
            boolean isContinue = false;
            for (int i = 0; i < len2; i++) {
                if (v[i] != 0) {
                    isContinue = true;
                    break;
                }
            }
            if (!isContinue)
                break;
            // 求余数
            int dig = 0;
            for (int i = 0; i < len2; i++) {
                dig = dig * 256 + v[i];
                v[i] = dig / baseNum;
                dig = dig % baseNum;
            }
            sb.append(base58Alphabet[dig]);

        }
        // 补充初始0
        for (int i = 0; i < numLeadZero; i++)
            sb.append(base58Alphabet[0]);
        sb.reverse();
        return sb.toString();
    }

    /**
     * 将Base58格式的数据转换为字节数组格式。
     *
     * @param str Base58格式的数据
     * @return 转换后的字节数组
     */
    public static byte[] decodeBase58(String str) throws NumberFormatException {
        // 计算初始的0的数量
        char[] data = str.toCharArray();
        int numleadZero = 0;
        int len = data.length;
        while (numleadZero < len && data[numleadZero] == base58Alphabet[0])
            numleadZero++;
        // 计算Base256的数值
        int len2 = len - numleadZero;
        int[] v = new int[len2];
        int baseNum = base58Alphabet.length;
        for (int i = numleadZero; i < len; i++) {
            // 乘以58
            for (int j = 0; j < len2; j++)
                v[j] *= baseNum;
            // 加上当前位
            Integer thisDigit = base58Value.get(data[i]);
            if (thisDigit == null)
                throw new NumberFormatException("Base58格式错误");
            v[len2 - 1] += thisDigit;
            // 整理
            int dig = 0;
            for (int j = len2 - 1; j >= 0; j--) {
                v[j] = v[j] + dig;
                dig = v[j] / 256;
                v[j] = v[j] % 256;
            }
        }
        // 转换成byte数组
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        for (int i = 0; i < numleadZero; i++)
            bout.write(0);
        int j = 0;
        while (j < len2 && v[j] == 0)
            j++;
        for (; j < len2; j++)
            bout.write(v[j]);
        return bout.toByteArray();
    }

    /**
     * 将字节数组表示的数据转换为可校验的Base58格式。
     *
     * @param data 字节数组表示的数据
     * @return Base58格式数据
     */
    public static String encodeBase58Verified(byte[] data) {
        byte[] checksum = encodeSha256Twice(data);
        byte[] f = new byte[data.length + 4];
        System.arraycopy(data, 0, f, 0, data.length);
        System.arraycopy(checksum, 0, f, data.length, 4);
        return encodeBase58(f);
    }

    /**
     * 将可校验的Base58格式的数据转换为字节数组格式。
     *
     * @param str Base58格式的数据
     * @return 转换后的字节数组
     */
    public static byte[] decodeBase58Verified(String str) throws NumberFormatException {
        byte[] f = decodeBase58(str);
        byte[] keyHash = ArrayUtils.subarray(f, 0, f.length - 4);
        byte[] checksum = ArrayUtils.subarray(f, f.length - 4, f.length);
        byte[] checksum2 = encodeSha256Twice(keyHash);
        checksum2 = ArrayUtils.subarray(checksum2, 0, 4);
        if (!Arrays.areEqual(checksum, checksum2))
            throw new NumberFormatException("checksum校验失败");
        return keyHash;
    }

    /**
     * 编码字符串所使用的乘数
     */
    private static final int ENCODE_WORD_MULTIPLIER = 27;
    /**
     * 允许编码的字符列表
     */
    public static final String ENCODE_WORD_ALPAHBET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    /**
     * 将字符串编码成为长整型格式。
     *
     * @param str 字符串
     * @return 编码后的长整型数
     */
    public static long encodeWord(String str) {
        String s = str.toUpperCase();
        if (!StringUtils.containsOnly(s, ENCODE_WORD_ALPAHBET))
            throw new IllegalArgumentException("字符串只允许包含A-Z");
        long t = 0;
        for (int i = 0; i < s.length(); i++) {
            t *= ENCODE_WORD_MULTIPLIER;
            int c = s.charAt(i) - 'A' + 1;
            t += c;
        }
        return t * 9999;
    }

    /**
     * 将长整型数解码成为字符串。
     *
     * @param val 长整型数
     * @return 解码后的字符串
     */
    public static String decodeWord(long val) {
        if (val % 9999 != 0)
            throw new IllegalArgumentException();
        val = val / 9999;
        StringBuilder sb = new StringBuilder();
        long t = val;
        while (t > 0) {
            long i = t % ENCODE_WORD_MULTIPLIER;
            if (i > ENCODE_WORD_MULTIPLIER || i < 1)
                throw new IllegalArgumentException();
            t = t / ENCODE_WORD_MULTIPLIER;
            char c = (char) ('A' + i - 1);
            sb.insert(0, c);
        }
        String str = sb.toString();
        return str;
    }

    /**
     * 将长整型数解码成为字符串，如果格式错误，返回默认字符串。
     *
     * @param val           长整型数
     * @param defaultString 默认字符串
     * @return 解码后的字符串
     */
    public static String decodeWord(long val, String defaultString) {
        try {
            return decodeWord(val);
        } catch (IllegalArgumentException e) {
            return defaultString;
        }
    }

    /**
     * 将密钥对保存至Keystore。
     *
     * @Deprecated 不用jks，使用keybox管理
     * @param alias        密钥名称
     * @param pwd          keystore密码
     * @param privateKey   私钥
     * @param certChain    证书链
     * @param keyStoreFile keystore文件
     * @throws KeyStoreException        KeyStore异常
     * @throws NoSuchAlgorithmException 加密算法异常
     * @throws CertificateException     证书异常
     * @throws IOException              输出输出异常
     */
    @Deprecated
    public static void saveToKeyStore(String alias, char[] pwd, PrivateKey privateKey, Certificate[] certChain,
            File keyStoreFile) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        // keyStore如果存在添加，keyStore如果不存在新增
        KeyStore keyStore = KeyStore.getInstance("JKS");
        if (keyStoreFile.exists()) {
            try (FileInputStream in = new FileInputStream(keyStoreFile)) {
                keyStore.load(in, pwd);
            }
        } else {
            keyStore.load(null, pwd);
        }
        keyStore.setKeyEntry(alias, privateKey, pwd, certChain);
        try (FileOutputStream out = new FileOutputStream(keyStoreFile)) {
            keyStore.store(out, pwd);
        }
    }

    private static byte[] toByte32(byte[] b) {
        if (b.length == 32)
            return b;
        else if (b.length > 32) {
            Validate.validState(b[0] == 0);
            return ArrayUtils.subarray(b, b.length - 32, b.length);
        } else {
            byte[] b2 = new byte[32];
            System.arraycopy(b, 0, b2, 32 - b.length, b.length);
            return b2;
        }
    }

    /**
     * 将base64格式RSA PRIVATE KEY 转换为私钥
     * 
     * @param key
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {
        final String BEGIN = "-----BEGIN RSA PRIVATE KEY-----\n";
        final String END = "\n-----END RSA PRIVATE KEY-----";
        String base64 = key.substring(key.indexOf(BEGIN) + BEGIN.length(), key.lastIndexOf(END));

        byte[] keyBytes;
        keyBytes = org.apache.commons.codec.binary.Base64.decodeBase64(base64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }
}
